package za.org.jcicada.deviceproxy.impl;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import za.org.jcicada.genss.messages.Result;
import za.org.jcicada.genss.messages.SuccessCode;
import za.org.jcicada.katcp.Client2;
import za.org.jcicada.katcp.Message;

/**
 * Responsible for checking for timeouts waiting for responses to commands. Keeps a map of command IDs and timer
 * tasks that is scheduled to be executed after a timeout period. When a response is received it is removed from the
 * map which effectively disables the timeout. If a response is not received then the timer task is executed and the
 * ID is removed from the map.
 */
public class RequestHandler {

    /** Logger object used for logging messages. */
    private static final Log logger = LogFactory.getLog(RequestHandler.class);

    /** Set of message names currently pending replies */
    private Map<String, TimerTask> map = new HashMap<String, TimerTask>();
    
    /** Timer object used to detect reply timeouts. */
    private Timer timer = new Timer("Timer-" + getClass().getCanonicalName());
    
    /** Handle to client object used to send messages. */
    private Client2 client;
    
    public static final long TIMEOUT_PERIOD = 2000L;
    
    public RequestHandler(Client2 client) {
        this.client = client;
    }
    
    public void shutdown() {
    	timer.cancel();
    }

    /**
     * Decorator for provided TimerTasks which is used to record the associated ID. When the timer is fired it
     * checks if the ID has been received. If not, then it delegates to the provided TimerTask and removes the ID
     * entry from the map.
     */
    private class IdTimerTask extends TimerTask {
        private String id;

        public IdTimerTask(String id) {
            this.id = id;
        }

        @Override
        public void run() {
            if (map.containsKey(id)) {
                logger.error(String.format("No reply received for ?%s", id));
                TimerTask task = map.get(id);
                if (task != null) {
                    task.run();
                }
                map.remove(id);
            }
        }

    }

    /**
     * Sends a message and starts a timeout timer to track message response
     * 
     * @param message The message to send
     */
    public Result send(Message message, TimerTask task) {
        try {
            client.sendMessage(message);
            timer.schedule(new IdTimerTask(message.getName()), TIMEOUT_PERIOD);
            map.put(message.getName(), task);
            return new Result();
        } catch (IOException ex) {
            String msg = "Unable to send message";
            logger.error(msg, ex);
            return new Result(SuccessCode.COMPLETE_FAILURE, msg);
        }
    }
    
    public Result send(Message message) {
        return send(message, null);
    }
    
    /**
     * Called on receipt of a message to disable the timeout handler.
     * 
     * @param message that has been received.
     */
    public void receive(Message message) {
        map.remove(message.getName());
    }
    
}

